-- 2022-8-5

--[[12.0
    Lua语言的标准库提供了两个用于操作日期和时间的函数，这两个函数在C语言标准库中也存在，提供的是同样的功能
    输入这两个函数看上去很简单，但依旧可以基于这些简单的功能完成很多复杂的工作

    Lua语言针对日期和时间使用两种表达方式
    第一种表达方式是一个数字，这个数字通常是一个整型数
    尽管并非是ISO C所必需的，但在大多数系统中这个数字是自一个被称为纪元(epoch)的固定日期后至今的秒数
    特别的，在POSIX和Windows系统中这个固定日期军事Jan01，1970，0：00 UTC

    Lua语言针对日期和时间提供的第二种表示方式是一个表
    日期表(date table)具有以下几个重要的字段:year,mouth,day,hour,min,sec.wday,yday.isdst,除isdst以外所有字段均为整型数
    前六个字段的含义非常明显，而wday字段表示本周中的第几天(第一天为星期天)，yday字段表示当年中的第几天(第一天是一月一日)
    isdst字段表示布尔类型，如果使用夏时令则为真
    例如 Sep 16，1998，23:48:10对应的表是
    {year = 1998,mouth = 9,day = 16,yday = 259,wday = 4,hour = 23,min = 48,sec = 10,isdst = false}
    日期表中不包括时区，程序需要负责结合相应的时区对其正确解析
]]

---[[12.1 os.time
    -- 不带任何参数调用函数os.time，会以数字形式返回当前的日期和时间:
    -- > os.time()     --> 1439653520

    -- 对应的时间是Aug 15,2015,12:45:20
    -- 在一个POSIX系统中，可以使用一些基本的数学分离这个数值
    local date = 1439653520
    local day2year = 365.242 -- 一年的天数
    local sec2hour = 60*60 -- 一小时的秒数
    local sec2day = sec2hour*24 -- 一天的秒数
    local sec2year = sec2day*day2year -- 一年的秒数

    -- 年
    print(date // sec2year + 1970) -- 2015.0
    -- 月份
    print(date%sec2day//sec2hour) -- 15
    -- 分钟
    print(date%sec2hour//60) -- 45
    -- 秒
    print(date%60) -- 20

    -- 如果以日期表作为参数调用函数os.time,那么该函数会返回该表中所描述日期和时间对应的数字
    -- year mouth 和 day字段是必需的，hour min和sec字段如果没有提供的话则默认为12:00:00,其余字段(包括wday和yday)则会被忽略

    -- print(os.time({year=2015,month=8,day=15,hour=12,min=45,sec=20}))
    -- print(os.time({year=1970,month=1,day=1,hour=0})) -- 10800
    -- print(os.time({year=1970,month=1,day=1}))
--]]

---[[12.2 os.date
    -- 函数os.date在一定程度上是函数os.time的反函数(尽管这个函数的名字写的是date)
    -- 它可以将一个表示日期和时间的数字转换为某些高级的表示形式，要么是日期表，要么是字符串
    -- 该函数的第一个参数是描述期望表示形式的格式化字符串，第二个参数的数字形式的日期和时间(如果不提供，则默认为当前日期和时间)

    -- 要生成一个日期表，可以使用格式化字符串 "*t" 例如，调用函数os.date("*t",906000490)会返回下列表
    -- {year=1998,month=9,day=16,yday=259,wday=4,hour=23,min=48,sec=10,isdst=false}
    local tab = os.date("*t",os.time())
    for key, value in pairs(tab) do
        print(key,value)
    end
    -- 大致上，对于任何有效的时间t,os.time(os.date("*t",t))==t均成立

    -- 除了isdst，结果中其余字段均为整型数且范围分别是
    -- year 一整年
    -- month 1-12
    -- day 1-31
    -- hour 0-23
    -- min 0-59
    -- sec 0-60
    -- wday 1-7 星期天是1
    -- yday 1-366
    -- (秒的最大最大范围是60，允许闰秒(leap second)的存在)

    -- 对于其他格式化字符串，函数os.date会将日期格式化为一个字符串，该字符串是根据指定的时间和日期信息对特定的指示符进行了替换的结果
    -- 所有的指示符都以百分号开头紧跟一个字母 例如
    print(os.date("a %A in %B"))
    print(os.date("%d/%m/%Y",90600490))
    -- 所有的表现形式取决于当前的区域设置，例如当前区域被设置为巴西-葡萄牙语时，%B会是 "maio"

    -- 主要的指示符如下
    -- %a 星期几的简写
    -- %A 星期几的全名
    -- %b 月份的简写
    -- %B 月份的全名
    -- %c 日期和时间
    -- %d 一个月中的第几天 (01-31)
    -- %H 24小时制中的小时数 (00-23)
    -- %I 12小时制中的小时数 (01-12)
    -- %j 一年中的第几天 (001-365)
    -- %m 月份 (01-12)
    -- %M 分钟 (00-59)
    -- %p 'am'或'pm'
    -- %S 秒数 (00-60)
    -- %w 星期几 (0-6=Sunday-Saturday)
    -- %W 一年中的第几周 (00-53)
    -- %x 日期
    -- %X 时间
    -- %y 两位数的年份 (00-99)
    -- %Y 完整的年份
    -- %z 时区
    -- %% 百分号

    -- 也给出了有效范围
    -- 以下是一些演示如何创建ISO 8601格式日期和时间的示例
    do
        local t = 906000490
        -- ISO 8601格式的日期
        print(os.date("%Y-%m-%d",t))
        -- ISO 8610格式的日期和时间
        print(os.date("%Y-%m-%dT%H:%M:%S",t))
        -- ISO 8601格式的序数日期
        print(os.date("%Y-%j",t))
    end
    -- 如果格式化字符串以叹号开头，那么函数os.date会以UTC格式对其进行解析:
    -- 纪元
    print(os.date("!%c",0)) -- 01/01/70 00:00:00
    print(os.date("%c",0)) -- 01/01/70 08:00:00
    -- 如果不带任何参数调用函数os.date，那么该函数会使用格式%c,即以一种合理的格式表示日期和时间信息
    -- 注意 %x %X和%c会根据不同的区域和系统而发生变化，如果需要诸如dd/mm/yyyy这样的固定表示形式，那么就必须显示的使用诸如"%d/%m/%Y"这样的格式化字符串
--]]

---[[12.3 日期和时间处理
    -- 当函数os.date创建日期表时，该表的所有字段均在有效的范围内
    -- 当我们给函数os.time传入一个日期表时，其中字段并不需要归一化 这个特性对于日期和时间处理非常重要

    -- 举一个简单的例子，假设想知道从当前向后数40天的日期，那么可以使用如下的代码进行计算
    local t = os.date("*t")
    print(os.date("%Y/%m/%d",os.time(t)))
    t.day = t.day + 40
    print(os.date("%Y/%m/%d",os.time(t)))

    -- 如果我们把数字表示的时间转换成日期表,那么就能得到日期和时间的归一化形式
    -- 下面实现了日期的倒退
    t = os.date("*t")
    print(t.day,t.month)
    t.day = t.day - 40
    print(t.day,t.month)
    t = os.date("*t",os.time(t))
    print(t.day,t.month)
    -- 在这个例子里，通过归一化，自动计算出了对应的日期

    -- 在大多数系统中，也可以对数字形式的时间增加或减少3456000(40天对应的秒数)
    -- 不过，由于标准C并不要求数值表示的时间是从纪元开始的，因此标准C并不保证这种操作的正确性
    -- 此外，如果我们想增加的是月份而非天数，由于不同的月份具有不同的天数，那么直接操作秒数就会有问题，而归一化的处理就没有这些问题
    t = os.date("*t") -- 获取当前时间
    print(os.date("%Y/%m/%d",os.time(t)))
    t.month = t.month + 6
    print(os.date("%Y/%m/%d",os.time(t)))

    -- 在操作日期时，我们必须要小心，虽然归一化是以显而易见的方式进行的，但是也可能会有一些不明显的后果
    -- 例如 如果计算March 31 之后的一个月，将会得到April 31，而实际上应该应该被归一化为May 1(April 30之后的一天)
    -- 尽管这种听上去很自然，但当我们May 1减去一个月得到的是April 1 而不是March 31
    -- 注意，这种不一致是日历机制导致的结果，与Lua语言无关

    -- 函数os.difftime用来计算两个时间之间的差值，该函数以秒返回两个指定数字形式表示的时间的差值
    -- 对于大多数系统而言 这个差值就是一个时间相对于另一个时间的减法结果
    -- 但是与减法不同，函数os.difftime的行为在任何系统中都是确定的
    -- 例如 计算lua5.2和lua5.3发布时间之间间隔的天数
    local t5_3 = os.time({year=2015,month=1,day=12})
    local t5_2 = os.time({year=2011,month=12,day=16})
    local d = os.difftime(t5_3,t5_2)
    print(d//(24*3600))

    -- 使用函数difftime可以获取指定日期相对任意时刻的秒数
    local myepoch = os.time({year=2000,month=1,day=1,hour=0})
    now = os.time({year = 2015,month = 11,day = 20})
    print(os.difftime(now,myepoch)) -- 501336000.0

    -- 通过归一化，可以很容易地将用秒表示的时间转换为合法的数字形式表示的时间
    -- 即我们可以创建一个带有开始时刻的日期表并将日期表中的秒数设置为想要转换的数字
    -- 例如
    T = {year = 2000,month = 1,day = 1,hour = 0}
    T.sec = 501336000
    print(os.date("%d/%m/%Y",os.time(T)))

    -- 我们还可以使用函数os.difftime来计算一段代码的执行时间
    -- 不过对于这个需求，更好的方式是使用函数os.clock，该函数会返回程序消耗的CPU时间(单位是秒)
    -- 函数os.clock在性能测试中的典型用法形如:
    local x = os.clock()
    local s = 0
    for i = 1, 100000000 do
        s = s + i
    end
    print(string.format("elapsed time: %.2f\n",os.clock()-x))
    -- 与os.time不同，os.clock通常具有比秒更高的精度，因此其返回值为一个浮点数
    -- 具体的精度与平台相关
--]]